package com.hongji.lms5xxcommunicator.opc.sub;

import com.hongji.lms5xxcommunicator.entity.SnapshotCache;
import com.hongji.lms5xxcommunicator.entity.ScanPointEntity;
import com.hongji.lms5xxcommunicator.exception.OpcConnectionException;
import com.hongji.lms5xxcommunicator.opc.trigger.TruckBedDetectorService;
import com.hongji.lms5xxcommunicator.realtime.PointDtoMapper;
import com.hongji.lms5xxcommunicator.realtime.WsTransferService;
import com.hongji.lms5xxcommunicator.realtime.websoket.SpringWsBroadcaster;
import com.hongji.lms5xxcommunicator.grid.RasterGrid;
import com.hongji.lms5xxcommunicator.grid.buff.InMemoryPointBuffer;
import com.hongji.lms5xxcommunicator.grid.config.RasterGridHolder;
import com.hongji.lms5xxcommunicator.grid.core.GridSpec;
import com.hongji.lms5xxcommunicator.grid.export.GridDeltaPayload;

import com.hongji.lms5xxcommunicator.mapper.ScanPointDataMapper;
import com.hongji.lms5xxcommunicator.opc.bootstrap.GridBootstrapper;
import com.hongji.lms5xxcommunicator.opc.bootstrap.GridSpecPublisher;
import com.hongji.lms5xxcommunicator.opc.trigger.TriggerExtremumService;
import com.hongji.lms5xxcommunicator.opc.util.OpcIoHelper;
import com.hongji.lms5xxcommunicator.opc.util.OpcNodeResolver;
import com.hongji.lms5xxcommunicator.service.ScanPointDataService;
import com.hongji.lms5xxcommunicator.service.UnifiedScanService;
import com.hongji.lms5xxcommunicator.storage.EpochManager;

import com.hongji.lms5xxcommunicator.grid.region.RegionRect;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class OpcSubscriptionServiceImp implements OpcSubscriptionService {

    // ========= 基础配置 =========
    @Value("${opc.endpoint:}") private String endpoint;
    @Value("${opc.request_timeout:15}") private int requestTimeoutSec;

    @Value("${opc.subscription_node_id:}") private String subscriptionNodeId; // 原有布尔启停
    @Value("${opc.trigger.flag_node_id:}") private String triggerFlagNodeId;  // 0/1/2/3
    @Value("${opc.trigger.sampling_interval:250}") private double triggerSamplingInterval;

    @Value("${bootstrap.page_size:5000}") private int bootstrapPageSize;
    @Value("${bootstrap.throttle_ms:30}") private int bootstrapThrottleMs;
    @Value("${ws.batch_size:5000}") private int wsBatchSize;

    // 仅用于日志；具体机型由 ScanStrategyFactory 根据 scanner.type 选择
    @Value("${scanner.type:}") private String scannerType;

    // 前端坐标映射常量
    @Value("${front.coord.y.base:2500}") private double viewYBase;    // Three.Y = base - y
    @Value("${front.coord.z.offset:400}") private double viewZOffset; // Three.Z = z + offset

    // ========= 区域与通用结果节点 =========
    @Value("${opc.trigger.region.xMin:}") private String regionXMinNode;
    @Value("${opc.trigger.region.xMax:}") private String regionXMaxNode;
    @Value("${opc.trigger.region.yMin:}") private String regionYMinNode;
    @Value("${opc.trigger.region.yMax:}") private String regionYMaxNode;

    @Value("${opc.trigger.result.mode_node_id:}") private String resultModeNode;
    @Value("${opc.trigger.result.x_node_id:}")    private String resultXNode;
    @Value("${opc.trigger.result.y_node_id:}")    private String resultYNode;
    @Value("${opc.trigger.result.z_node_id:}")    private String resultZNode;

    @Value("${opc.trigger.region.unit_scale:1.0}") private double regionUnitScale; // PLC→计算
    @Value("${opc.trigger.db.unit_scale:1.0}")     private double dbUnitScale;     // DB→计算
    @Value("${opc.trigger.max_points:200000}")     private int triggerMaxPoints;

    // === 车斗识别（code==3）参数 ===
    @Value("${opc.trigger.truck_bed.enabled:true}")              private boolean truckBedEnabled;
    @Value("${opc.trigger.truck_bed.z_bin_size:0.03}")           private double truckBedZBinSize;
    @Value("${opc.trigger.truck_bed.z_tolerance:0.02}")          private double truckBedZTol;
    @Value("${opc.trigger.truck_bed.exclude_bottom_percent:0.20}") private double truckBedExcludeBottomPct;
    @Value("${opc.trigger.truck_bed.min_points:600}")            private int truckBedMinPlanePoints;
    @Value("${opc.trigger.truck_bed.fit_tilt_plane:false}")      private boolean truckBedFitTiltPlane;

    // === 分段循环输出（中心点应答）===
    @Value("${opc.trigger.truck_bed.segment.max_len_m:2.0}")     private double truckBedSegMaxLenM;
    @Value("${opc.trigger.truck_bed.segment.center_req_node_id:ns=4;i=263}") private String truckBedCenterReqNodeId;
    @Value("${opc.trigger.truck_bed.segment.poll_ms:50}")        private int truckBedSegPollMs;
    @Value("${opc.trigger.truck_bed.segment.max_wait_ms_per_center:5000}") private int truckBedSegMaxWaitMs;

    @Value("${grid.export.row-origin:bottom}")
    private String rowOrigin; // bottom | top
    private boolean isBottomOrigin() { return !"top".equalsIgnoreCase(rowOrigin); }

    // ========= 依赖 =========
    private final UnifiedScanService unifiedScanService;
    private final ScanPointDataMapper scanPointDataMapper;
    private final InMemoryPointBuffer pointBuffer;
    private final RasterGridHolder rasterGridHolder;
    private final ScanPointDataService scanPointDataService;

    private final SpringWsBroadcaster wsBroadcaster;
    private final SnapshotCache snapshotCache;

    private final EpochManager epochManager;

    // 读取 yml 排除区所需
    private final Environment env;

    // ========= 运行时 =========
    private volatile OpcUaClient client;
    private volatile UaSubscription subscription;
    private volatile UaMonitoredItem startStopItem;
    private volatile UaMonitoredItem triggerItem;

    private final OpcNodeResolver nodeResolver = new OpcNodeResolver();

    private final ScheduledExecutorService scheduler =
            Executors.newSingleThreadScheduledExecutor(r -> {
                Thread t = new Thread(r, "opc-bootstrap");
                t.setDaemon(true);
                return t;
            });

    private TriggerExtremumService triggerSvc;      // 极值服务（code 1/2）
    private TruckBedDetectorService truckBedSvc;    // 车斗识别（code 3）
    private WsTransferService      wsTransfer;

    // ========= 生命周期 =========
    @Override
    @PostConstruct
    public void start() {
        try {
            if (isBlank(subscriptionNodeId) && isBlank(triggerFlagNodeId)) {
                log.warn("未配置订阅节点，跳过 OPC 订阅创建");
                return;
            }
            client = createClient(endpoint);
            dumpOpcContextSafe();

            subscription = client.getSubscriptionManager().createSubscription(250.0).get();
            log.info("订阅创建成功，ID={}", subscription.getSubscriptionId());

            // 业务服务
            OpcIoHelper io = new OpcIoHelper(client, nodeResolver);

            // ====== 读取排除区（计算单位）并构造极值服务 ======
            List<RegionRect> excludeRectsCalc = loadExcludeRectsFromEnv();
            triggerSvc = new TriggerExtremumService(
                    io, scanPointDataMapper,
                    regionXMinNode, regionXMaxNode, regionYMinNode, regionYMaxNode,
                    resultModeNode, resultXNode, resultYNode, resultZNode,
                    regionUnitScale, dbUnitScale, triggerMaxPoints,
                    excludeRectsCalc
            );

            // 车斗识别服务（code: 3）—保持你的原始构造
            truckBedSvc = new TruckBedDetectorService(
                    io, scanPointDataMapper,
                    // 区域节点
                    regionXMinNode, regionXMaxNode, regionYMinNode, regionYMaxNode,
                    // 通用结果节点
                    resultModeNode, resultXNode, resultYNode, resultZNode,
                    // 单位与限流
                    regionUnitScale, dbUnitScale, triggerMaxPoints,
                    // 平面/OBB 参数
                    truckBedZBinSize, truckBedZTol, truckBedExcludeBottomPct, truckBedMinPlanePoints, truckBedFitTiltPlane,
                    // 分段参数
                    truckBedSegMaxLenM, truckBedCenterReqNodeId, truckBedSegPollMs, truckBedSegMaxWaitMs
            );

            // 前端传输（统一行起点）
            PointDtoMapper pointDtoMapper = new PointDtoMapper(viewYBase, viewZOffset, isBottomOrigin());
            wsTransfer = new WsTransferService(wsBroadcaster, snapshotCache, pointDtoMapper, wsBatchSize);

            // 监听项
//            if (!isBlank(subscriptionNodeId)) {
            if (true) { //
                    startStopItem = MonitoredItemHelper.addWithCallback(
                        subscription, subscriptionNodeId, triggerSamplingInterval,
                        this::handleStartStop, nodeResolver, client
                ).get(0);
                log.info("监控项已添加（大车启停）：{}", subscriptionNodeId);
            }
            if (!isBlank(triggerFlagNodeId)) {
                triggerItem = MonitoredItemHelper.addWithCallback(
                        subscription, triggerFlagNodeId, triggerSamplingInterval,
                        this::handleModuleTrigger, nodeResolver, client
                ).get(0);
                log.info("监控项已添加（模块A触发 0/1/2/3）：{}", triggerFlagNodeId);
            }

            // 推网格规格 & 首帧快照
            new GridSpecPublisher(rasterGridHolder, wsTransfer).publishOnce();
            scheduler.submit(new GridBootstrapper(
                    bootstrapPageSize, bootstrapThrottleMs,
                    scanPointDataMapper, rasterGridHolder,
                    wsTransfer, pointDtoMapper,
                    epochManager
            )::bootstrapStreamFromDb);

        } catch (Exception ex) {
            log.error("OPC 初始化失败", ex);
        }
    }

    @Override
    @PreDestroy
    public void stop() {
        log.info("释放 OPC 订阅资源…");
        try {
            scheduler.shutdownNow();
            if (startStopItem != null && subscription != null) {
                subscription.deleteMonitoredItems(Collections.singletonList(startStopItem)).get();
            }
            if (triggerItem != null && subscription != null) {
                subscription.deleteMonitoredItems(Collections.singletonList(triggerItem)).get();
            }
            if (subscription != null && client != null) {
                client.getSubscriptionManager().deleteSubscription(subscription.getSubscriptionId()).get();
            }
        } catch (Exception e) {
            log.warn("释放订阅资源时异常：{}", e.getMessage());
        } finally {
            try { unifiedScanService.stop(); } catch (Exception ignore) {}
            safeDisconnect(client);
            subscription = null;
            startStopItem = null;
            triggerItem = null;
            client = null;
        }
    }

    // ========= 回调 =========
    private void handleStartStop(UaMonitoredItem item, DataValue val) {
        Object raw = (val != null && val.getValue() != null) ? val.getValue().getValue() : null;
        boolean start = (raw instanceof Boolean) ? (Boolean) raw
                : (raw instanceof Number) ? ((Number) raw).intValue() != 0
                : Boolean.parseBoolean(String.valueOf(raw));
        log.info("获取到大车启停信息为：{}", start);

        if (start) {
            // 取出缓冲中的原始点（不要再做二次栅格化）
            List<ScanPointEntity> rawBatch = pointBuffer.drainToList(wsBatchSize);
            if (rawBatch != null && !rawBatch.isEmpty()) {
                RasterGrid grid = rasterGridHolder.get();
                GridDeltaPayload payload = grid.mergeWithDelta(rawBatch, null);
                wsTransfer.pushDelta(payload, grid);
            }

            // 启动统一扫描
            tryStartScanner();
        } else {
            unifiedScanService.stop();
            onPlcStopSignal();
        }
    }

    private void handleModuleTrigger(UaMonitoredItem item, DataValue val) {
        Object raw = (val != null && val.getValue() != null) ? val.getValue().getValue() : null;
        int code = parseIntSafe(raw);  // 0/1/2/3
        log.info("模块A触发码：{}", code);

        if (code == 3) {
            if (!truckBedEnabled) {
                log.warn("车斗识别未启用（opc.trigger.truck_bed.enabled=false）");
                return;
            }
            if (truckBedSvc == null) {
                log.warn("truckBedDetectorService 未初始化");
                return;
            }
            try {
                truckBedSvc.onTriggerTruckBed();  // 计算 + 分段中心点循环输出
            } catch (Exception e) {
                log.error("车斗识别失败：{}", e.getMessage(), e);
            }
            return;
        }

        // 原有 0/1/2 极值流程
        if (triggerSvc != null) {
            triggerSvc.onTrigger(code);
        } else {
            log.warn("TriggerExtremumService 未初始化");
        }
    }

    /** 停机尾差落库：补齐 ix/iy/lastFrame（x/y/z 保持原始），用 (ix,iy) 覆盖去抖 */
    private void onPlcStopSignal() {
        List<ScanPointEntity> tail = pointBuffer.snapshotAndClear();
        if (tail == null || tail.isEmpty()) {
            log.info("缓冲为空，停机无需落库");
            return;
        }

        RasterGrid grid = rasterGridHolder.get();
        if (grid == null || grid.spec() == null) {
            log.warn("RasterGrid 未初始化，直接原样落库（可能导致后续还原条纹）");
        } else {
            final long frameMark = System.currentTimeMillis();
            final GridSpec spec = grid.spec();

            tail.removeIf(e -> {
                if (e == null || e.getX() == null || e.getY() == null) return true;
                int ix = spec.toCol(e.getX());
                int iy = spec.toRow(e.getY());
                if (ix < 0 || iy < 0) return true;  // 超出网格范围直接丢弃
                e.setIx(ix);
                e.setIy(iy);
                e.setLastFrame(frameMark);
                return false;
            });
        }

        if (tail.isEmpty()) {
            log.info("停机尾差无有效点（越界或空），跳过落库");
            return;
        }

        int n = scanPointDataService.batchUpsertPoints(tail);
        log.info("停机尾差落库完成：{} 条（含 ix/iy 覆盖去抖）", n);
    }

    /** 统一启动扫描：内部策略根据配置 scanner.type 决定 LMS / PICS */
    private void tryStartScanner() {
        try {
            unifiedScanService.start();
            log.info("统一扫描已启动（strategy by scanner.type='{}'）", scannerType);
        } catch (Exception e) {
            log.error("启动统一扫描失败", e);
        }
    }

    // ========= OPC 客户端 =========
    private OpcUaClient createClient(String endpoint) throws Exception {
        EndpointDescription ep = DiscoveryClient.getEndpoints(endpoint).get().stream()
                .filter(e -> {
                    UserTokenPolicy[] tokens = e.getUserIdentityTokens();
                    if (tokens == null) return false;
                    for (UserTokenPolicy t : tokens) {
                        if (t != null && t.getTokenType() == UserTokenType.Anonymous) return true;
                    }
                    return false;
                })
                .findFirst()
                .orElseThrow(() -> new OpcConnectionException("未找到匿名端点"));

        OpcUaClientConfig cfg = OpcUaClientConfig.builder()
                .setEndpoint(ep)
                .setIdentityProvider(new AnonymousProvider())
                .setRequestTimeout(Unsigned.uint(requestTimeoutSec * 1000L))
                .build();

        OpcUaClient cli = OpcUaClient.create(cfg);
        cli.connect().get(30, TimeUnit.SECONDS);
        log.info("已连接 OPC 服务器：{}", endpoint);
        return cli;
    }

    private void dumpOpcContextSafe() {
        try {
            String url = (client.getConfig()!=null && client.getConfig().getEndpoint()!=null)
                    ? client.getConfig().getEndpoint().getEndpointUrl() : "(unknown)";
            log.info("OPC Endpoint = {}", url);
        } catch (Exception e) {
            log.warn("打印 OPC 上下文失败：{}", e.toString());
        }
    }

    private void safeDisconnect(OpcUaClient c) {
        if (c != null) try { c.disconnect().get(3, TimeUnit.SECONDS); } catch (Exception ignored) {}
    }

    private static boolean isBlank(String s){ return s==null || s.trim().isEmpty(); }
    private static int parseIntSafe(Object v){
        if (v == null) return 0;
        if (v instanceof Number) return ((Number) v).intValue();
        try { return Integer.parseInt(String.valueOf(v).trim()); } catch (Exception e) { return 0; }
    }

    // ====== 读取排除区（计算单位）======
    private List<RegionRect> loadExcludeRectsFromEnv() {
        Binder b = Binder.get(env);
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> rectMaps = (List<Map<String, Object>>) (List<?>)
                b.bind("opc.trigger.exclude.rects", Bindable.listOf(Map.class))
                        .orElse(Collections.emptyList());

        List<RegionRect> excludes = new ArrayList<>();
        for (Map<String, Object> m : rectMaps) {
            if (m == null) continue;
            Double xMin = toD(m.get("xMin"));
            Double xMax = toD(m.get("xMax"));
            Double yMin = toD(m.get("yMin"));
            Double yMax = toD(m.get("yMax"));
            if (xMin == null || xMax == null || yMin == null || yMax == null) continue;
            double a = xMin, b1 = xMax, c = yMin, d = yMax;
            if (b1 < a) { double t=a; a=b1; b1=t; }
            if (d  < c) { double t=c; c=d;  d=t; }
            excludes.add(new RegionRect(a, b1, c, d));
        }
        log.info("排除矩形数量：{}", excludes.size());
        return excludes;
    }

    private static Double toD(Object v) {
        if (v == null) return null;
        if (v instanceof Number) return ((Number) v).doubleValue();
        try { return Double.parseDouble(String.valueOf(v)); } catch (Exception e) { return null; }
    }
}
